package br.ufmg.dcc.vod.remoteworkers.processor;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

import br.edu.ufcg.lsd.commune.api.FailureNotification;
import br.edu.ufcg.lsd.commune.api.InvokeOnDeploy;
import br.edu.ufcg.lsd.commune.api.MonitoredBy;
import br.edu.ufcg.lsd.commune.api.RecoveryNotification;
import br.edu.ufcg.lsd.commune.container.servicemanager.ServiceManager;
import br.edu.ufcg.lsd.commune.identification.ServiceID;
import br.ufmg.dcc.vod.remoteworkers.Command;
import br.ufmg.dcc.vod.remoteworkers.ID;
import br.ufmg.dcc.vod.remoteworkers.Result;
import br.ufmg.dcc.vod.remoteworkers.scheduler.CommandInterested;

public class ProcessorImpl implements Processor {

	public static final String OBJECT_NAME = "PROCESSOR";
	protected static final String INTERNAL_HANDLE = UUID.randomUUID().toString();
	protected static final String WORK_ACTION_NAME = "WA";
	
	private long token;
	private ServiceManager serviceManager;
	
	private CommandInterested master;
	
	private Res result;
	private ServiceID masterID;
	
	public ProcessorImpl() {
		resetToken();
	}
	
	@InvokeOnDeploy
	public void init(ServiceManager serviceManager) {
		this.serviceManager = serviceManager;
	}
	
	private void resetToken() {
		masterID = null;
		master = null;
		result = null;
		token = IDLE_TOKEN;
	}
	
	private void updateToken() {
		token = (long) (Math.random() * Long.MAX_VALUE);
	}
	
	@Override
	public void process(@MonitoredBy(OBJECT_NAME) CommandInterested interested, Command command) {
		if (token == IDLE_TOKEN) {
			updateToken();
			interested.iAmWorking(token, this);
			masterID = serviceManager.getStubDeploymentID(interested).getServiceID();
			serviceManager.registerInterest(OBJECT_NAME, masterID.toString(), CommandInterested.class);
			serviceManager.addActionForRepetition(WORK_ACTION_NAME, new WorkAction());
			serviceManager.scheduleActionToRunOnce(WORK_ACTION_NAME, 0, TimeUnit.MICROSECONDS, new WorkHandle(command, INTERNAL_HANDLE));
			master = interested;
		} else { //best effort, let the dude suffer
			interested.commandException(command.getID(), new NotIdleException());
		}
	}

	@RecoveryNotification
	public void interestedUp(CommandInterested interested) {
		ServiceID stubID = serviceManager.getStubDeploymentID(interested).getServiceID();
		
		if (token != IDLE_TOKEN && masterID != null && masterID.equals(stubID)) {
			master = interested;
			masterID = stubID;
			if (result != null) {
				sendResult();
			}
		}
	}
	
	@FailureNotification
	public void interestedDown(CommandInterested interested) {
		ServiceID stubID = serviceManager.getStubDeploymentID(interested).getServiceID();
		
		if (token != IDLE_TOKEN && masterID != null && masterID.equals(stubID)) {
			resetToken();
		}
	}

	@Override
	public void workDone(WorkHandle handler, ID id, Result result, Exception e) {
		if (token != IDLE_TOKEN && handler.getInternal().equals(INTERNAL_HANDLE)) {
			this.result = new Res(id, result, e);
			
			if (master != null) {
				sendResult();
			}
		}
	}

	private void sendResult() {
		if (result.e != null) {
			master.commandException(result.id, result.e);
		} else {
			master.commandResult(result.id, result.res);
		}
	}

	@Override
	public void release(long token) {
		if (token != IDLE_TOKEN && token == this.token) {
			resetToken();
		}
	}

	protected long getToken() {
		return token;
	}
	
	protected CommandInterested getMaster() {
		return master;
	}
	
	protected ServiceID getMasterID() {
		return masterID;
	}
	
	private class Res {

		private final ID id;
		private final Result res;
		private final Exception e;

		public Res(ID id, Result result, Exception e) {
			this.id = id;
			this.res = result;
			this.e = e;
		}
		
	}
}